4. 쿠버네티스 개요3

3.4.3. 데몬셋 (DaemonSet)

데몬셋이란?

데몬셋의 개념:

디플로이먼트는 기본적으로 파드 그룹을 특정 노드가 아니라 클러스터 전체에서 N개 가동한다는 형태로 관리. 하지만 노드의 로그 수집, 모니터링처럼 노드와 밀접한 관계가 있는 애플리케이션이라면 각 노드에 데몬 프로세스처럼 1대씩 배포하는 쪽이 적절함

용어 정리

  • 데몬셋(DaemonSet): 클러스터의 모든(또는 일부) 노드에 Pod를 하나씩 실행하도록 보장하는 컨트롤러
  • 데몬 프로세스(Daemon Process): 백그라운드에서 지속적으로 실행되는 서비스 프로세스. 시스템 시작 시 자동 실행
  • 노드 에이전트(Node Agent): 각 노드에서 실행되며 노드 관련 작업(로그 수집, 모니터링 등)을 수행하는 프로그램

Deployment vs DaemonSet:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[Deployment]
    클러스터 전체에 N개 배포
    ↓
    [Node1: Pod1, Pod2] [Node2: Pod3] [Node3: (없음)]

    특징:
    ├─ 스케줄러가 최적 노드 선택
    ├─ 노드별 파드 개수 불균등 가능
    └─ replicas로 총 개수 지정

vs

[DaemonSet]
    각 노드마다 1개씩 배포
    ↓
    [Node1: Pod1] [Node2: Pod2] [Node3: Pod3]

    특징:
    ├─ 모든 노드에 파드 1개씩 자동 배치
    ├─ 노드 추가 시 자동으로 파드 생성
    └─ replicas 설정 없음 (노드 수 = 파드 수)

데몬셋의 특징:

데몬셋은 각 노드마다 파드가 하나씩 실행된 상태를 유지하는 기능

DaemonSet 동작 방식:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[초기 상태]
    Node1, Node2, Node3 존재
    ↓
[DaemonSet 배포]
    각 노드에 자동으로 파드 1개씩 생성
    ├─ Node1 → Pod1
    ├─ Node2 → Pod2
    └─ Node3 → Pod3
    ↓
[노드 추가]
    Node4가 클러스터에 추가됨
    ↓
[자동 확장]
    Node4 → Pod4 (자동 생성)
    ↓
[파드 삭제 시]
    Node1의 Pod1 삭제됨
    ↓
[자동 복구]
    Node1 → Pod1-new (자동 재생성)

데몬셋의 주요 사용 사례

시스템 레벨 애플리케이션:

DaemonSet 활용 예시:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[로그 수집]
    ├─ Fluentd
    ├─ Logstash
    └─ Filebeat

    이유: 모든 노드의 로그를 수집해야 함

[모니터링]
    ├─ Prometheus Node Exporter
    ├─ Datadog Agent
    └─ Grafana Agent

    이유: 각 노드의 메트릭 수집 필요

[네트워크]
    ├─ kube-proxy
    ├─ Calico
    └─ Cilium

    이유: 각 노드의 네트워크 설정 관리

[스토리지]
    ├─ Ceph
    ├─ GlusterFS
    └─ Longhorn

    이유: 분산 스토리지 노드 구성

[보안]
    ├─ Falco (침입 탐지)
    ├─ Tetragon (보안 관찰)
    └─ SELinux Agent

    이유: 모든 노드의 보안 모니터링
용어 정리

  • Fluentd: 로그 수집 및 전달을 위한 오픈소스 도구. 다양한 소스에서 로그를 수집하여 목적지로 전송
  • Prometheus Node Exporter: 노드(서버)의 하드웨어 및 OS 메트릭을 수집하여 Prometheus로 전송하는 에이전트
  • Calico/Cilium: Kubernetes 네트워크 플러그인(CNI). 네트워크 정책 및 라우팅 관리
  • Falco: 컨테이너 런타임의 보안 위협을 실시간으로 탐지하는 오픈소스 도구


데몬셋 예제

기본 데몬셋 매니페스트:

파일명: ubuntu-daemonset.yaml

apiVersion: apps/v1 # Kubernetes API 버전
kind: DaemonSet # 리소스 타입 - DaemonSet (각 노드마다 파드 1개씩 배포)
metadata:
  name: ubuntu-daemonset # 데몬셋 이름
spec:
  selector: # 어떤 파드를 관리할지 선택하는 조건
    matchLabels:
      app: ubuntu # "app=ubuntu" 레이블을 가진 파드 관리
  template: # 파드 생성 시 사용할 템플릿
    metadata:
      labels:
        app: ubuntu # 생성되는 파드에 부여할 레이블
    spec: # 파드 스펙 정의
      containers:
        - name: ubuntu # 컨테이너 이름
          image: ubuntu:22.04 # Ubuntu 22.04 LTS 이미지
          command: ["sh"] # 실행할 명령어
          args:
            - -euc # 옵션: -e (에러 시 종료), -u (미정의 변수 에러), -c (문자열을 명령어로 실행)
            - "sleep infinity" # 컨테이너를 무한 대기 상태로 유지 (종료 방지)

데몬셋 배포:

# 데몬셋 배포
kubectl apply -f ubuntu-daemonset.yaml

# 데몬셋 상태 확인
kubectl get daemonsets

# 파드 확인 (-o wide로 어느 노드에 배치되었는지 확인)
kubectl get pods -l=app=ubuntu -o wide

출력 예시:

NAME                     READY   STATUS    RESTARTS   AGE   NODE
ubuntu-daemonset-abc12   1/1     Running   0          10s   node1
ubuntu-daemonset-def34   1/1     Running   0          10s   node2
ubuntu-daemonset-ghi56   1/1     Running   0          10s   node3

각 노드에 파드가 정확히 1개씩 배치된 것을 확인할 수 있음


데몬셋의 고급 기능

특정 노드에만 배포:

노드 셀렉터를 사용하여 특정 레이블을 가진 노드에만 데몬셋 파드 배포 가능

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: ssd-monitor
spec:
  selector:
    matchLabels:
      app: ssd-monitor
  template:
    metadata:
      labels:
        app: ssd-monitor
    spec:
      nodeSelector: # 특정 노드에만 배포
        disk-type: ssd # "disk-type=ssd" 레이블을 가진 노드에만 배포
      containers:
        - name: monitor
          image: monitor:latest
노드 셀렉터 동작:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

클러스터 노드:
├─ Node1 (labels: disk-type=ssd)      → Pod 배포 ✓
├─ Node2 (labels: disk-type=hdd)      → Pod 배포 ✗
├─ Node3 (labels: disk-type=ssd)      → Pod 배포 ✓
└─ Node4 (레이블 없음)                → Pod 배포 ✗

결과: SSD 노드에만 모니터링 파드 배포
용어 정리

  • 노드 셀렉터(nodeSelector): Pod를 특정 레이블을 가진 노드에만 배치하도록 지정하는 간단한 방법

Toleration을 사용한 마스터 노드 배포:

기본적으로 마스터 노드(Control Plane)에는 파드가 배포되지 않음. Toleration을 사용하면 마스터 노드에도 배포 가능

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: network-plugin
spec:
  selector:
    matchLabels:
      app: network
  template:
    metadata:
      labels:
        app: network
    spec:
      tolerations: # Taint를 무시하고 배포
        - key: node-role.kubernetes.io/control-plane # 마스터 노드의 Taint 키
          effect: NoSchedule # NoSchedule Taint 무시
          operator: Exists # 키가 존재하기만 하면 허용
      containers:
        - name: network
          image: calico/node:latest
Taint와 Toleration:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[Taint (오염)]
    노드에 제약을 걸어 특정 파드만 배치
    │
    예시: 마스터 노드
    └─ node-role.kubernetes.io/control-plane:NoSchedule
       (일반 파드 배치 금지)
    ↓
[Toleration (용인)]
    파드가 Taint를 무시하고 배치될 수 있도록 허용
    │
    예시: 네트워크 플러그인
    └─ tolerations:
         - key: node-role.kubernetes.io/control-plane
           effect: NoSchedule
    ↓
결과: 마스터 노드를 포함한 모든 노드에 배포
용어 정리

  • Taint(테인트): 노드에 제약을 걸어 특정 조건을 만족하는 Pod만 배치되도록 하는 기능. "오염"이라는 의미
  • Toleration(톨러레이션): Pod가 특정 Taint를 무시하고 해당 노드에 배치될 수 있도록 허용하는 설정. "용인"이라는 의미
  • NoSchedule: 해당 Taint를 용인하지 않는 Pod는 이 노드에 스케줄링되지 않음을 의미하는 effect


롤링 업데이트

데몬셋도 롤링 업데이트 지원:

디플로이먼트처럼 데몬셋도 무중단 롤링 업데이트 가능

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-collector
spec:
  updateStrategy: # 업데이트 전략 설정
    type: RollingUpdate # 롤링 업데이트 방식
    rollingUpdate:
      maxUnavailable: 1 # 업데이트 중 최대 1개 파드만 사용 불가 (한 번에 1개씩 업데이트)
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      containers:
        - name: fluentd
          image: fluent/fluentd:v1.16 # 이미지 버전

업데이트 프로세스:

DaemonSet 롤링 업데이트:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

초기 상태 (v1.15)
    [Node1: Pod v1.15] [Node2: Pod v1.15] [Node3: Pod v1.15]
    ↓
업데이트 시작 (v1.15 → v1.16)
    [Node1: Pod v1.15 삭제] [Node2: Pod v1.15] [Node3: Pod v1.15]
    maxUnavailable: 1 (최대 1개 노드만 파드 없음)
    ↓
    [Node1: Pod v1.16 생성] [Node2: Pod v1.15] [Node3: Pod v1.15]
    ↓
    [Node1: Pod v1.16] [Node2: Pod v1.15 삭제] [Node3: Pod v1.15]
    ↓
    [Node1: Pod v1.16] [Node2: Pod v1.16 생성] [Node3: Pod v1.15]
    ↓
최종 상태 (v1.16)
    [Node1: Pod v1.16] [Node2: Pod v1.16] [Node3: Pod v1.16]

업데이트 명령어:

# 이미지 업데이트
kubectl set image daemonset/log-collector fluentd=fluent/fluentd:v1.16

# 롤아웃 상태 확인
kubectl rollout status daemonset/log-collector

# 롤아웃 히스토리 확인
kubectl rollout history daemonset/log-collector

# 이전 버전으로 롤백
kubectl rollout undo daemonset/log-collector

데몬셋 vs 디플로이먼트 비교

배포 방식 비교:

항목 DaemonSet Deployment
배포 방식 각 노드마다 1개 클러스터 전체에 N개
replicas 설정 불가 (자동으로 노드 수) 설정 필요
스케줄링 모든 노드에 강제 배치 스케줄러가 최적 노드 선택
노드 추가 시 자동으로 파드 생성 변화 없음
노드 제거 시 해당 파드 자동 삭제 다른 노드로 재배치
용도 시스템 레벨 서비스 애플리케이션 서비스
롤링 업데이트 지원 지원

사용 사례 비교:

배포 타입 선택 가이드:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[DaemonSet 사용]
    질문: 이 애플리케이션이 모든 노드에서 실행되어야 하나요?
    ├─ Yes → DaemonSet
    │
    예시:
    ├─ 로그 수집 (모든 노드의 로그 필요)
    ├─ 모니터링 (모든 노드의 메트릭 필요)
    ├─ 네트워크 플러그인 (모든 노드의 네트워크 설정)
    └─ 보안 에이전트 (모든 노드 보안 감시)

[Deployment 사용]
    질문: 애플리케이션 인스턴스가 몇 개 필요한가요?
    ├─ N개 → Deployment
    │
    예시:
    ├─ 웹 서버 (3개의 인스턴스로 로드 밸런싱)
    ├─ API 서버 (5개의 복제본)
    ├─ 백그라운드 워커 (2개의 워커)
    └─ 마이크로서비스 (각 서비스별 N개)

실습: 노드 정보 수집 데몬셋 예제

간단한 노드 모니터링 데몬셋:

각 노드의 시스템 정보를 수집하여 출력하는 간단한 데몬셋 예제. Elasticsearch 같은 외부 서비스 없이 독립적으로 동작함

파일명: node-monitor-daemonset.yaml

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-monitor
  labels:
    app: node-monitor
spec:
  selector:
    matchLabels:
      app: node-monitor
  template:
    metadata:
      labels:
        app: node-monitor
    spec:
      tolerations: # 마스터 노드에도 배포
        - key: node-role.kubernetes.io/control-plane
          effect: NoSchedule
          operator: Exists
      containers:
        - name: monitor
          image: busybox:1.36
          command:
            - sh
            - -c
            - |
              echo "=== Node Monitor Started on $(hostname) ==="
              while true; do
                echo "--- $(date) ---"
                echo "Hostname: $(hostname)"
                echo "Uptime: $(uptime)"
                echo "Memory: $(free -m 2>/dev/null || cat /proc/meminfo | head -3)"
                echo "Disk: $(df -h / 2>/dev/null | tail -1)"
                echo ""
                sleep 30
              done
          resources:
            limits:
              memory: 64Mi
              cpu: 50m
            requests:
              memory: 32Mi
              cpu: 10m

배포 및 확인:

# 데몬셋 배포
kubectl apply -f node-monitor-daemonset.yaml

# 데몬셋 상태 확인
kubectl get daemonsets

# 각 노드별 파드 배치 확인
kubectl get pods -l app=node-monitor -o wide

# 특정 파드의 로그 확인 (노드 정보 출력됨)
kubectl logs -l app=node-monitor --tail=20

# 실시간 로그 확인
kubectl logs -l app=node-monitor -f

출력 예시:

=== Node Monitor Started on node-monitor-abc12 ===
--- Wed Jan 15 12:00:00 UTC 2026 ---
Hostname: node-monitor-abc12
Uptime:  12:00:00 up 3 days,  2:30,  0 users,  load average: 0.15, 0.10, 0.05
Memory:              total        used        free
Mem:          7982        4521        1203
Disk: /dev/sda1       50G   12G   35G  26% /

리소스 정리:

kubectl delete -f node-monitor-daemonset.yaml

(참고) 프로덕션 로그 수집 데몬셋 예제

실제 프로덕션 환경에서 Fluentd를 사용한 로그 수집 구성 예시. Elasticsearch 클러스터가 먼저 구성되어 있어야 동작함

# 주의: Elasticsearch가 먼저 배포되어 있어야 함
# 학습용으로만 참고하고, 실제 실행은 Elasticsearch 구성 후 진행

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    app: fluentd-logging
spec:
  selector:
    matchLabels:
      app: fluentd-logging
  template:
    metadata:
      labels:
        app: fluentd-logging
    spec:
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          effect: NoSchedule
      containers:
        - name: fluentd
          image: fluent/fluentd-kubernetes-daemonset:v1.16-debian-elasticsearch8-1
          env:
            - name: FLUENT_ELASTICSEARCH_HOST
              value: "elasticsearch.logging.svc.cluster.local"
            - name: FLUENT_ELASTICSEARCH_PORT
              value: "9200"
          resources:
            limits:
              memory: 200Mi
            requests:
              cpu: 100m
              memory: 200Mi
          volumeMounts:
            - name: varlog
              mountPath: /var/log
            - name: containers
              mountPath: /var/lib/docker/containers
              readOnly: true
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: containers
          hostPath:
            path: /var/lib/docker/containers
Fluentd + Elasticsearch 구성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[Node 1]              [Node 2]              [Node 3]
    │                     │                     │
[Fluentd Pod]        [Fluentd Pod]        [Fluentd Pod]
    │                     │                     │
    ├─ /var/log 수집      ├─ /var/log 수집      ├─ /var/log 수집
    └─ 컨테이너 로그 수집   └─ 컨테이너 로그 수집   └─ 컨테이너 로그 수집
         │                     │                     │
         └─────────────────────┼─────────────────────┘
                               ↓
                    [Elasticsearch 클러스터]
                               ↓
                         [Kibana UI]
                               ↓
                       로그 검색 및 시각화

3.4.4. 잡 (Job)과 크론잡 (CronJob)

잡 (Job)이란?

배치 작업을 위한 리소스:

파드를 배치 작업처럼 단발적으로 실행하는 사례에 유용한 잡. 디플로이먼트나 데몬셋과 달리 잡은 파드를 지속적으로 실행하지 않고, 작업 완료 후 종료됨

용어 정리

  • 잡(Job): 한 번 실행 후 완료되는 배치 작업을 관리하는 Kubernetes 컨트롤러
  • 배치 작업(Batch Job): 한 번에 대량의 데이터를 처리하거나 특정 작업을 일괄 수행하는 프로그램
  • 종료 상태 코드(Exit Code): 프로그램 종료 시 반환하는 숫자. 0은 성공, 0이 아닌 값은 실패를 의미

Deployment vs Job:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[Deployment]
    파드를 계속 실행 (무한 루프)
    │
    ├─ 웹 서버: 요청 대기 중
    ├─ API 서버: 요청 처리 중
    └─ 데이터베이스: 연결 대기 중

    파드 종료 시: 자동 재시작 (항상 실행 상태 유지)

vs

[Job]
    파드를 한 번 실행하고 종료
    │
    ├─ 데이터 백업: 백업 완료 후 종료
    ├─ 데이터 마이그레이션: 마이그레이션 완료 후 종료
    └─ 보고서 생성: 생성 완료 후 종료

    파드 종료 시: 재시작 안 함 (완료 상태로 유지)

잡의 기능:

잡은 다음 항목을 설정할 수 있음:

Job 설정 옵션:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

성공 조건
    ├─ completions: 최소한 성공해야 하는 파드 개수
    └─ parallelism: 동시 실행 파드 개수

실패 처리
    ├─ backoffLimit: 허용 가능 실패 횟수
    └─ restartPolicy: 실패 시 재시작 정책 (Never, OnFailure)

타임아웃
    └─ activeDeadlineSeconds: 최대 실행 시간
용어 정리

  • completions: Job이 성공으로 완료되기 위해 성공해야 하는 Pod의 총 개수
  • parallelism: 동시에 실행할 수 있는 Pod의 최대 개수
  • backoffLimit: Job이 실패로 처리되기 전까지 허용되는 재시도 횟수
  • restartPolicy: Pod 실패 시 재시작 정책. Job에서는 Never(재시작 안 함) 또는 OnFailure(실패 시만 재시작)만 허용

쿠버네티스는 이에 따라 파드를 배포하고 실행. 파드 종료 상태 코드를 보고 성공 여부를 판단해서 재실행하는 등의 처리


잡 예제

기본 잡 매니페스트:

파일명: ubuntu-job.yaml

apiVersion: batch/v1 # Batch API 버전 (Job, CronJob용)
kind: Job # 리소스 타입 - Job (단발성 작업 실행)
metadata:
  name: job-example # 잡 이름
spec:
  completions: 3 # 3개의 파드가 정상 종료하면 잡 성공 (순차 실행)
  parallelism: 1 # 동시에 실행할 파드 개수 (1개씩 순차 실행)
  backoffLimit: 4 # 최대 재시도 횟수 (4번 실패하면 잡 실패 처리)
  template: # 파드 템플릿
    spec:
      restartPolicy: Never # 파드 실패 시 재시작 안 함 (Job에서는 Never 또는 OnFailure만 허용)
      containers:
        - name: test # 컨테이너 이름
          image: ubuntu:22.04 # Ubuntu 이미지
          command: ["sh"] # 실행할 명령어
          args:
            - -euc # 옵션
            - "echo done $(hostname)" # 호스트명 출력 후 종료

잡 실행:

# 잡 배포
kubectl apply -f ubuntu-job.yaml

# 잡 상태 실시간 확인 (-w는 watch 모드)
kubectl get jobs job-example -w -o custom-columns=NAME:.metadata.name,SUCCEEDED:.status.succeeded,FAILED:.status.failed

출력 예시:

NAME          SUCCEEDED   FAILED
job-example   0           0
job-example   1           0
job-example   2           0
job-example   3           0
Job 실행 과정:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

초기 상태
    completions: 3
    parallelism: 1
    ↓
[Pod 1 실행]
    echo done job-example-abc12
    → 성공 (exit code 0)
    SUCCEEDED: 1/3
    ↓
[Pod 2 실행]
    echo done job-example-def34
    → 성공 (exit code 0)
    SUCCEEDED: 2/3
    ↓
[Pod 3 실행]
    echo done job-example-ghi56
    → 성공 (exit code 0)
    SUCCEEDED: 3/3
    ↓
[잡 완료]
    모든 파드 성공적으로 종료

파드 확인:

# 잡이 생성한 파드 목록 확인
kubectl get pods -l batch.kubernetes.io/job-name=job-example

출력 예시:

NAME                READY   STATUS      RESTARTS   AGE
job-example-abc12   0/1     Completed   0          2m
job-example-def34   0/1     Completed   0          1m
job-example-ghi56   0/1     Completed   0          30s

파드 그룹이 종료되어도 잡은 삭제되지 않고 남음. 따라서 잡에 속한 각 파드 로그를 실행한 후에도 확인할 수 있음

로그 확인:

# 잡의 모든 파드 로그 확인
kubectl logs -l batch.kubernetes.io/job-name=job-example

출력 예시:

done job-example-abc12
done job-example-def34
done job-example-ghi56

잡 삭제:

# 잡과 관련 파드 모두 삭제
kubectl delete -f ubuntu-job.yaml

# 또는
kubectl delete job job-example

병렬 잡 (Parallel Job)

여러 파드를 동시에 실행:

파일명: parallel-job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: parallel-job
spec:
  completions: 10 # 총 10개의 파드가 성공해야 잡 완료
  parallelism: 3 # 동시에 3개의 파드를 병렬 실행
  backoffLimit: 5 # 최대 5번까지 재시도
  template:
    spec:
      restartPolicy: OnFailure # 실패 시 파드 재시작 (Never 대신 사용 가능)
      containers:
        - name: worker
          image: busybox:1.36
          command:
            - sh
            - -c
            - |
              echo "Worker started: $(hostname)"
              sleep $((RANDOM % 10 + 5))  # 5~15초 랜덤 대기
              echo "Worker completed: $(hostname)"
병렬 잡 실행 과정:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

초기 상태
    completions: 10 (총 10개 성공 필요)
    parallelism: 3 (동시에 3개 실행)
    ↓
[1단계: 3개 병렬 실행]
    Pod1 실행 중...
    Pod2 실행 중...
    Pod3 실행 중...
    ↓
[Pod2 완료]
    Pod1 실행 중...
    Pod3 실행 중...
    Pod4 실행 시작 (즉시 보충)
    SUCCEEDED: 1/10
    ↓
[Pod1, Pod3 완료]
    Pod4 실행 중...
    Pod5 실행 시작
    Pod6 실행 시작
    SUCCEEDED: 3/10
    ↓
[반복...]
    ↓
[최종 완료]
    SUCCEEDED: 10/10

실행 및 모니터링:

# 병렬 잡 배포
kubectl apply -f parallel-job.yaml

# 실시간 모니터링 (Active는 현재 실행 중인 파드 수)
kubectl get jobs parallel-job -w

# 파드 상태 확인
kubectl get pods -l batch.kubernetes.io/job-name=parallel-job -w

잡 실패 처리

실패 시나리오와 처리:

파일명: failing-job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: failing-job
spec:
  completions: 3
  backoffLimit: 2 # 최대 2번까지만 재시도
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: test
          image: busybox:1.36
          command:
            - sh
            - -c
            - exit 1 # 항상 실패 (exit code 1)
실패 잡 동작 과정:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[Pod 1 실행]
    exit 1 → 실패
    FAILED: 1
    ↓
[Pod 2 실행] (재시도 1)
    exit 1 → 실패
    FAILED: 2
    ↓
[Pod 3 실행] (재시도 2)
    exit 1 → 실패
    FAILED: 3 (backoffLimit 초과)
    ↓
[잡 실패]
    STATUS: Failed
    더 이상 재시도 안 함

배포 및 확인:

# 실패 잡 배포
kubectl apply -f failing-job.yaml

# 잡 상태 실시간 확인
kubectl get jobs failing-job -w

# 파드 상태 확인 (Error 상태 확인)
kubectl get pods -l batch.kubernetes.io/job-name=failing-job

# 잡 상세 정보 (실패 원인 확인)
kubectl describe job failing-job

출력 예시:

NAME          STATUS   COMPLETIONS   DURATION   AGE
failing-job   Failed   0/3           15s        15s

NAME                  READY   STATUS   RESTARTS   AGE
failing-job-abc12     0/1     Error    0          15s
failing-job-def34     0/1     Error    0          10s
failing-job-ghi56     0/1     Error    0          5s

리소스 정리:

kubectl delete -f failing-job.yaml

타임아웃 설정

activeDeadlineSeconds 사용:

파일명: timeout-job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: timeout-job
spec:
  completions: 1
  activeDeadlineSeconds: 60 # 60초 이내에 완료되어야 함
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: test
          image: busybox:1.36
          command:
            - sh
            - -c
            - sleep 100 # 100초 대기 (60초 타임아웃으로 실패)
타임아웃 잡 동작 과정:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[잡 시작]
    activeDeadlineSeconds: 60
    ↓
[Pod 실행]
    sleep 100 (100초 대기 시작)
    ↓
[60초 경과]
    타임아웃 발생
    ↓
[잡 강제 종료]
    STATUS: Failed
    reason: DeadlineExceeded

배포 및 확인:

# 타임아웃 잡 배포
kubectl apply -f timeout-job.yaml

# 잡 상태 실시간 확인 (60초 후 Failed로 변경됨)
kubectl get jobs timeout-job -w

# 파드 상태 확인
kubectl get pods -l batch.kubernetes.io/job-name=timeout-job

# 잡 상세 정보 (DeadlineExceeded 확인)
kubectl describe job timeout-job

출력 예시 (60초 후):

NAME          STATUS   COMPLETIONS   DURATION   AGE
timeout-job   Failed   0/1           60s        60s

# describe 출력에서 확인 가능:
# Type     Reason            Message
# ----     ------            -------
# Warning  DeadlineExceeded  Job was active longer than specified deadline

리소스 정리:

kubectl delete -f timeout-job.yaml

크론잡 (CronJob)

크론잡이란?

주기적으로 실행되는 잡:

크론잡은 잡을 제어해서 크론 형식으로 시작 시간과 주기적 실행 방법을 설정하는 기능

용어 정리

  • 크론잡(CronJob): Job을 지정된 스케줄에 따라 주기적으로 생성하는 Kubernetes 컨트롤러
  • 크론(cron): Unix/Linux 시스템의 시간 기반 작업 스케줄러. 특정 시간에 명령을 실행
  • 크론 표현식(Cron Expression): 작업 실행 시간을 정의하는 형식. "분 시 일 월 요일" 5개 필드로 구성

Job vs CronJob:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[Job]
    한 번만 실행
    ↓
    kubectl apply -f job.yaml
    ↓
    작업 실행 → 완료 → 종료

vs

[CronJob]
    정해진 스케줄에 따라 반복 실행
    ↓
    kubectl apply -f cronjob.yaml
    ↓
    매시간 0분: 잡 생성 → 실행 → 완료
    (반복)

크론 표현식:

크론 스케줄 형식:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

* * * * *
│ │ │ │ │
│ │ │ │ └─ 요일 (0-6, 0=일요일)
│ │ │ └─── 월 (1-12)
│ │ └───── 일 (1-31)
│ └─────── 시 (0-23)
└───────── 분 (0-59)

예시:
0 * * * *        # 매시간 0분
*/15 * * * *     # 매 15분마다
0 2 * * *        # 매일 오전 2시
0 0 * * 0        # 매주 일요일 자정
0 0 1 * *        # 매월 1일 자정
30 3 * * 1-5     # 평일 오전 3시 30분

크론잡 예제

기본 크론잡 매니페스트:

파일명: backup-cronjob.yaml

apiVersion: batch/v1 # Kubernetes API 버전
kind: CronJob # 리소스 타입 - CronJob (주기적으로 잡 실행)
metadata:
  name: backup-cronjob # 크론잡 이름
spec:
  schedule: "0 2 * * *" # 크론 표현식: 매일 오전 2시에 실행
  jobTemplate: # 생성할 잡의 템플릿
    spec:
      completions: 1 # 잡당 1개의 파드만 성공하면 됨
      template: # 파드 템플릿
        spec:
          restartPolicy: OnFailure # 실패 시 재시작
          containers:
            - name: backup # 컨테이너 이름
              image: ubuntu:22.04 # 이미지
              command:
                - sh
                - -c
                - |
                  echo "Backup started at $(date)"
                  echo "Backing up database..."
                  sleep 10
                  echo "Backup completed at $(date)"

크론잡 배포:

# 크론잡 배포
kubectl apply -f backup-cronjob.yaml

# 크론잡 목록 확인
kubectl get cronjobs

# 크론잡 상세 정보
kubectl describe cronjob backup-cronjob

출력 예시:

NAME             SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
backup-cronjob   0 2 * * *     False     0        <none>          10s

크론잡 고급 설정

히스토리 관리:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: cleanup-cronjob
spec:
  schedule: "*/5 * * * *" # 5분마다 실행
  successfulJobsHistoryLimit: 3 # 성공한 잡 3개만 보관
  failedJobsHistoryLimit: 1 # 실패한 잡 1개만 보관
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: cleanup
              image: busybox:1.36
              command:
                - sh
                - -c
                - echo "Cleanup completed"
히스토리 관리:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

시간 경과:
05:00 → Job1 성공
05:05 → Job2 성공
05:10 → Job3 성공
05:15 → Job4 성공 (Job1 자동 삭제, 최근 3개만 유지)
05:20 → Job5 성공 (Job2 자동 삭제)

보관된 잡: Job3, Job4, Job5
삭제된 잡: Job1, Job2

동시 실행 정책:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: long-running-cronjob
spec:
  schedule: "* * * * *" # 매분 실행
  concurrencyPolicy: Forbid # 동시 실행 금지
  # Allow: 동시 실행 허용 (기본값)
  # Forbid: 이전 잡이 실행 중이면 새 잡 건너뛰기
  # Replace: 이전 잡 종료하고 새 잡 실행
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: worker
              image: busybox:1.36
              command:
                - sh
                - -c
                - sleep 120 # 2분 대기 (다음 스케줄과 겹침)
동시 실행 정책 비교:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[Allow - 동시 실행 허용]
10:00 → Job1 시작 (2분 소요)
10:01 → Job2 시작 (Job1 실행 중)
10:02 → Job1 완료, Job3 시작 (Job2 실행 중)

[Forbid - 동시 실행 금지]
10:00 → Job1 시작 (2분 소요)
10:01 → 건너뜀 (Job1 실행 중)
10:02 → Job1 완료, Job2 시작

[Replace - 기존 잡 교체]
10:00 → Job1 시작 (2분 소요)
10:01 → Job1 종료, Job2 시작
10:02 → Job2 종료, Job3 시작
용어 정리

  • concurrencyPolicy: CronJob의 동시 실행 정책. Allow(허용), Forbid(금지), Replace(교체) 중 선택
  • successfulJobsHistoryLimit: 성공한 Job 히스토리를 몇 개까지 보관할지 지정
  • failedJobsHistoryLimit: 실패한 Job 히스토리를 몇 개까지 보관할지 지정
  • startingDeadlineSeconds: 스케줄 시간 이후 Job을 시작해야 하는 마감 시간(초)

시작 기한 설정:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: strict-schedule-cronjob
spec:
  schedule: "0 3 * * *" # 매일 오전 3시
  startingDeadlineSeconds: 300 # 5분 이내에 시작 못 하면 건너뛰기
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: task
              image: busybox:1.36
              command: ["sh", "-c", "echo Task completed"]
시작 기한 동작:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

스케줄: 03:00
startingDeadlineSeconds: 300 (5분)

시나리오 1: 정상 시작
03:00 → 스케줄 시간
03:00:10 → 잡 시작 (10초 지연, OK)

시나리오 2: 지연 시작
03:00 → 스케줄 시간
03:04:30 → 잡 시작 (4분 30초 지연, OK)

시나리오 3: 마감 초과
03:00 → 스케줄 시간
03:06:00 → 마감 초과 (6분 지연, 건너뛰기)

크론잡 수동 실행

크론잡에서 즉시 잡 생성:

# 크론잡에서 수동으로 잡 생성
kubectl create job --from=cronjob/backup-cronjob manual-backup

# 생성된 잡 확인
kubectl get jobs

# 잡 로그 확인
kubectl logs job/manual-backup

실전 예제: 데이터베이스 백업 크론잡 (참고용, 기반 리소스 없이 실행하면 에러)

MySQL 백업 크론잡:

파일명: mysql-backup-cronjob.yaml

apiVersion: batch/v1
kind: CronJob
metadata:
  name: mysql-backup
  namespace: production
spec:
  schedule: "0 1 * * *" # 매일 오전 1시
  successfulJobsHistoryLimit: 7 # 일주일치 보관
  failedJobsHistoryLimit: 3 # 실패 3개 보관
  concurrencyPolicy: Forbid # 동시 백업 금지
  jobTemplate:
    spec:
      backoffLimit: 3 # 3번 재시도
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: mysql-backup
              image: mysql:8.0
              env:
                - name: MYSQL_HOST
                  value: "mysql.production.svc.cluster.local"
                - name: MYSQL_USER
                  valueFrom:
                    secretKeyRef:
                      name: mysql-secret
                      key: username
                - name: MYSQL_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: mysql-secret
                      key: password
              command:
                - sh
                - -c
                - |
                  BACKUP_FILE="/backup/mysql-$(date +%Y%m%d-%H%M%S).sql"
                  echo "Starting backup to $BACKUP_FILE"
                  mysqldump -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD --all-databases > $BACKUP_FILE
                  echo "Backup completed: $(ls -lh $BACKUP_FILE)"
              volumeMounts:
                - name: backup-storage
                  mountPath: /backup
          volumes:
            - name: backup-storage
              persistentVolumeClaim:
                claimName: backup-pvc

크론잡 vs 외부 크론

쿠버네티스 크론잡 vs 리눅스 cron:

항목 쿠버네티스 CronJob 리눅스 cron
선언적 관리 YAML로 버전 관리 가능 crontab 파일 (수동 관리)
고가용성 컨트롤 플레인이 자동 관리 단일 서버 (SPOF)
리소스 제한 메모리/CPU 제한 가능 제한 없음 (시스템 리소스)
로그 관리 kubectl logs로 쉽게 확인 syslog 또는 별도 설정 필요
확장성 여러 노드에서 실행 가능 단일 서버에서만 실행
모니터링 Kubernetes 메트릭 통합 별도 모니터링 필요

참고 자료

공식 문서:

베스트 프랙티스: